package com.tinem.platform.web.auth.config;

import com.tinem.platform.module.pojo.co.GatewayHeadName;
import com.tinem.platform.module.pojo.co.RedisKeyEnum;
import com.tinem.platform.module.starter.web.filter.CorsFilter;
import com.tinem.platform.web.auth.grant.client_credentials_custom.ClientCredentialsCustomAuthenticationSecurityConfig;
import com.tinem.platform.web.auth.grant.client_credentials_user.ClientCredentialsUserAuthenticationSecurityConfig;
import com.tinem.platform.web.auth.login.code.CodeAuthenticationFilter;
import com.tinem.platform.web.auth.login.code.CodeAuthenticationSecurityConfig;
import com.tinem.platform.web.auth.login.oauth2.ext.OAuth2AuthenticationSecurityConfig;
import com.tinem.platform.web.auth.server.client.password.PlatformPasswordEncoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.IOException;
import java.time.Duration;

/**
 * @author fengzhihao
 * EnableGlobalMethodSecurity 开启全局方法安全注解
 */
@Configuration
@SuppressWarnings("unchecked")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    DataSource dataSource;
    UserDetailsService userDetailsService;

    @Value("${spring.security.login.success.page}")
    String loginProcessUrl;
    @Value("${spring.security.login.remember-me}")
    Duration rememberMe;

    @Resource
    AuthenticationSuccessHandler jsonAuthenticationSuccessHandler;
    @Resource
    AuthenticationFailureHandler jsonAuthenticationFailureHandler;
    @Resource(name = "oauthUserService")
    private OAuth2UserService oAuth2UserService;
    @Resource(name = "oidUserService")
    private OidcUserService oidcUserService;
    @Autowired
    private PersistentTokenRepository persistentTokenRepository;

    @Value("${spring.application.name}")
    String clientId;
    @Resource
    StringRedisTemplate stringRedisTemplate;
    @Resource
    CodeAuthenticationSecurityConfig codeAuthenticationSecurityConfig;
    @Resource
    ClientCredentialsUserAuthenticationSecurityConfig clientCredentialsUserAuthenticationSecurityConfig;
    @Resource
    ClientCredentialsCustomAuthenticationSecurityConfig clientCredentialsCustomAuthenticationSecurityConfig;
    @Resource
    OAuth2AuthenticationSecurityConfig oAuth2AuthenticationSecurityConfig;

    public WebSecurityConfig(
            @Autowired DataSource dataSource
            ,@Qualifier("userDetailsService") @Autowired UserDetailsService userDetailsService
    ){
        this.dataSource = dataSource;
        this.userDetailsService = userDetailsService;
    }
    @Override
    public UserDetailsService userDetailsService() {
        return userDetailsService;
    }

    @Override
    @Bean(name= BeanIds.AUTHENTICATION_MANAGER)
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    @Primary
    public PasswordEncoder passwordEncoder() {
        return new PlatformPasswordEncoder();
    }

    /**
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .cors().disable()
                .authorizeRequests()
                    .antMatchers(   "/webjars/**").permitAll()
                    .antMatchers(   "/endpoint/**").permitAll()
                    .antMatchers(   "/login/oauth2/code/**").permitAll()
                    .antMatchers(   "/verification/code/**").permitAll()
                    .antMatchers(   "/amazeui/**").permitAll()
                    .antMatchers(   "/assets/**").permitAll()
                    .antMatchers(   "/favicon.ico").permitAll()
                    .antMatchers(   "/jquery/**").permitAll()
                    .antMatchers(  "/actuator").permitAll()
                    .antMatchers(  "/actuator/**").permitAll()
                    .antMatchers(  "/oauth/authorize").permitAll()
                    .antMatchers(  "/oauth/token_key").permitAll()
                    .antMatchers(  "/oauth/jwks.json").permitAll()
                    .antMatchers(  "/login.do").permitAll()
                    .antMatchers(CodeAuthenticationFilter.LOGIN_PROCESSING).permitAll()
                    .anyRequest().authenticated()
                .and ().exceptionHandling().accessDeniedPage("/commons/403")
                .and().httpBasic().disable()
                .rememberMe()
                    .tokenRepository(persistentTokenRepository)
                    .tokenValiditySeconds((int) rememberMe.getSeconds())
                    .userDetailsService(userDetailsService)
                .and()
                .formLogin()
                    .successHandler(jsonAuthenticationSuccessHandler)
                    .failureHandler(jsonAuthenticationFailureHandler)
                    .loginPage("/login.do")
                .and().apply(codeAuthenticationSecurityConfig)
                .and().apply(clientCredentialsUserAuthenticationSecurityConfig)
                .and().apply(clientCredentialsCustomAuthenticationSecurityConfig)
                .and().apply(oAuth2AuthenticationSecurityConfig)
//                .and().logout().logoutUrl("/logout.do")
                .and().oauth2Login()
                    .userInfoEndpoint()
                    .userService(oAuth2UserService)
                    .oidcUserService(oidcUserService)
        ;
    }

    @Bean
    public FilterRegistrationBean reqFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setOrder(-1000);
        filterRegistrationBean.setFilter(new Filter() {
            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                final HttpServletRequest request = (HttpServletRequest) servletRequest;
                final HttpServletResponse response = (HttpServletResponse) servletResponse;
                String requestId = request.getHeader(GatewayHeadName.X_PLATFORM_GATEWAY_REQ_API_REQUEST_ID);
                stringRedisTemplate.opsForHash().put(
                        RedisKeyEnum.gateway_req_info.getKey(requestId)
                        , GatewayHeadName.X_PLATFORM_GATEWAY_CONTEXT_CLIENT_ID
                        , clientId);
                filterChain.doFilter(servletRequest,servletResponse);
            }
        });
        filterRegistrationBean.setName("reqFilter");
        filterRegistrationBean.addUrlPatterns("*.do","*.api");
        return filterRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean corsFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setOrder(-999);
        filterRegistrationBean.setFilter(new CorsFilter());
        filterRegistrationBean.setName("corsFilter");
        filterRegistrationBean.addUrlPatterns("*.do","*.api","*.notify","*");
        return filterRegistrationBean;
    }
}